﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PersonalExpenseTracker.PersistenceLayer.Xml;

namespace PersonalExpenseTracker.PersistenceLayer
{
    public static class PersistenceLayerFacade
    {
        private static DataStore ds;

        /// <summary>
        /// Cache application data.
        /// </summary>
        public static void Initialize()
        {
            if (ds == null)
            {
                ds = new Repository().LoadDataStore();
            }
        }

        #region Categories Management

        /// <summary>
        /// Returns all categories for drop down list binding.
        /// </summary>
        public static List<FlyWeightItem> GetAllCategories()
        {
            List<FlyWeightItem> items = new List<FlyWeightItem>();

            List<FlyWeightItem> cats = GetRootCategories();

            foreach (FlyWeightItem item in cats)
            {
                items.Add(item);
                GetAllChildCategories(items, item.Id, item.Name);
            }

            return items;
        }

        private static void GetAllChildCategories(List<FlyWeightItem> items, int parentCategoryId, string parentCategoryName)
        {
            List<FlyWeightItem> cats = GetChildCategories(parentCategoryId);

            for (int i = 0; i < cats.Count; i++)
            {
                FlyWeightItem item = cats[i];
                item.Name = parentCategoryName + " - " + item.Name;
                items.Add(item);
                GetAllChildCategories(items, item.Id, item.Name);
            }
        }

        /// <summary>
        /// Returns all root categories for tree view binding.
        /// </summary>
        public static List<FlyWeightItem> GetRootCategories()
        {
            return GetCategoriesByParentId(0);
        }

        /// <summary>
        /// Returns sub categories for a specified category for tree view binding.
        /// </summary>
        public static List<FlyWeightItem> GetChildCategories(int parentCategoryId)
        {
            return GetCategoriesByParentId(parentCategoryId);
        }

        private static List<FlyWeightItem> GetCategoriesByParentId(int parentId)
        {
            Initialize();
            List<FlyWeightItem> cats = new List<FlyWeightItem>(ds.Category.Count);

            var query = (from o in ds.Category
                         where o.ParentCategoryId == parentId
                         orderby o.CategoryName
                         select new
                         {
                             o.CategoryId,
                             o.CategoryName
                         });

            foreach (var record in query)
            {
                FlyWeightItem item = new FlyWeightItem();
                item.Id = record.CategoryId;
                item.Name = record.CategoryName;
                cats.Add(item);
            }

            return cats;
        }

        /// <summary>
        /// Adds a new category.
        /// </summary>
        /// <returns>Generated category id.</returns>
        public static int AddCategory(string categoryName, int parentId)
        {
            Initialize();

            DataStore.CategoryRow row = ds.Category.NewCategoryRow();
            row.CategoryName = categoryName;
            row.ParentCategoryId = parentId;
            ds.Category.AddCategoryRow(row);

            Repository rep = new Repository();
            rep.UpdateDataStore(ds);

            return row.CategoryId;
        }

        /// <summary>
        /// Updates an existing category.
        /// </summary>
        public static void UpdateCategory(int categoryId, string categoryName, int parentId)
        {
            Initialize();

            DataStore.CategoryRow row =
                ds.Category.FindByCategoryId(categoryId);

            row.CategoryName = categoryName;

            Repository rep = new Repository();
            rep.UpdateDataStore(ds);
        }

        #endregion

        #region Expense Mangement

        /// <summary>
        /// Adds or updates an Expense.
        /// </summary>
        public static bool SaveExpense(Expense expense)
        {
            Initialize();

            DataStore.ExpenseRow row;

            if (expense.ExpenseId == -1)
            {
                // new expense
                row = ds.Expense.NewExpenseRow();
                expense.ExpenseId = row.ExpenseId;
                ds.Expense.AddExpenseRow(row);
            }
            else
            {
                row = ds.Expense.FindByExpenseId(expense.ExpenseId);

                if (row == null)
                {
                    System.Diagnostics.Debug.WriteLine("Row is not found.", "Persistence Layer");
                    return false;
                }
            }

            row.Amount = expense.Amount;
            row.CategoryId = expense.CategoryId;
            row.Description = expense.Description;
            row.ExpenseDate = expense.Date;

            Repository rep = new Repository();
            rep.UpdateDataStore(ds);

            return true;
        }

        public static List<Expense> GetThisMonthExpenses()
        {
            int year = DateTime.Now.Year;
            int month = DateTime.Now.Month;
            DateTime startDate = new DateTime(year, month, 1, 0, 0, 0, 0);
            DateTime endDate = new DateTime(year, month, 
                DateTime.DaysInMonth(year, month), 23, 59, 59, 999);
            return GetExpensesByDate(startDate, endDate);
        }

        public static List<Expense> GetThisYearExpenses()
        {
            int year = DateTime.Now.Year;
            DateTime startDate = new DateTime(year, 1, 1, 0, 0, 0, 0);
            DateTime endDate = new DateTime(year, 12,
                DateTime.DaysInMonth(year, 12), 23, 59, 59, 999);
            return GetExpensesByDate(startDate, endDate);
        }

        public static List<Expense> GetAllExpenses()
        {
            DateTime startDate = DateTime.MinValue;
            DateTime endDate = DateTime.MaxValue;
            return GetExpensesByDate(startDate, endDate);
        }

        public static List<Expense> GetExpensesByDate(DateTime startDate, DateTime endDate)
        {
            Initialize();
            List<Expense> expenses = new List<Expense>(ds.Expense.Count);

            var query = (from o in ds.Expense
                         join c in ds.Category
                         on o.CategoryId equals c.CategoryId
                         where o.ExpenseDate >= startDate
                         && o.ExpenseDate <= endDate
                         orderby o.ExpenseDate descending
                         select new
                         {
                             o.Amount, 
                             o.CategoryId, 
                             o.Description, 
                             o.ExpenseDate, 
                             o.ExpenseId, 
                             c.CategoryName
                         });

            foreach (var record in query)
            {
                Expense item = new Expense();
                item.Amount = record.Amount;
                item.CategoryId = record.CategoryId;
                item.Date = record.ExpenseDate;
                item.Description = record.Description;
                item.ExpenseId = record.ExpenseId;
                item.CategoryName = record.CategoryName;
                expenses.Add(item);
            }

            return expenses;
        }

        public static Expense GetExpenseById(int id)
        {
            Initialize();
            
            DataStore.ExpenseRow row = ds.Expense.FindByExpenseId(id);

            if (row == null)
            {
                System.Diagnostics.Debug.WriteLine("Row is not found.", "Persistence Layer");
                return new Expense();
            }

            Expense expense = new Expense();
            expense.ExpenseId = row.ExpenseId;
            expense.Amount = row.Amount;
            expense.CategoryId = row.CategoryId;
            expense.Description = row.Description;
            expense.Date = row.ExpenseDate;

            return expense;
        }


        #endregion

        #region Summary Management

        public static decimal GetThisMonthTotalExpense()
        {
            int year = DateTime.Now.Year;
            int month = DateTime.Now.Month;
            DateTime startDate = new DateTime(year, month, 1, 0, 0, 0, 0);
            DateTime endDate = new DateTime(year, month,
                DateTime.DaysInMonth(year, month), 23, 59, 59, 999);
            return GetExpenseAmountByDate(startDate, endDate);
        }

        public static decimal GetThisYearTotalExpense()
        {
            int year = DateTime.Now.Year;
            DateTime startDate = new DateTime(year, 1, 1, 0, 0, 0, 0);
            DateTime endDate = new DateTime(year, 12,
                DateTime.DaysInMonth(year, 12), 23, 59, 59, 999);
            return GetExpenseAmountByDate(startDate, endDate);
        }

        public static decimal GetAllExpenseAmount()
        {
            return GetExpenseAmountByDate(DateTime.MinValue, DateTime.MaxValue);
        }

        public static decimal GetExpenseAmountByDate(DateTime startDate, DateTime endDate)
        {
            Initialize();

            var query = (from o in ds.Expense.AsEnumerable()
                         where o.ExpenseDate >= startDate
                         && o.ExpenseDate <= endDate
                         select new
                         {
                             o.Amount
                         });

            decimal amount = 0;

            foreach (var record in query)
            {
                amount += record.Amount;
            }

            return amount;
        }



        #endregion
    }
}
